缺失值(Missing Values)是数据分析中无法回避的普遍问题。
本章核心任务:学会识别、诊断和处理数据框中的缺失值。
| 场景 | 说明 |
|---|---|
| 停牌事件 | 股票因重大事项停牌,期间无交易数据 |
| 新股上市 | 上市前无历史数据,产生”左截断”问题 |
| 节假日效应 | A股春节、国庆休市,海外市场继续交易 |
| 数据源故障 | 数据供应商技术问题导致部分数据不可用 |
| 公司退市 | 退市公司被剔除,历史数据不再维护 |
Pandas使用 NaN(Not a Number)表示数值型缺失值。
NaN的特殊性质:
NaN != NaN(自反性不成立)NaN + x = NaN(吸收律:任何运算结果仍为NaN)== 判断NaN,应使用 pd.isna() 或 np.isnan()import numpy as np
import pandas as pd
nan_value = np.nan
# NaN的比较特性
print('NaN的比较特性:')
print(f'NaN == NaN: {nan_value == nan_value}') # False!
print(f'NaN != NaN: {nan_value != nan_value}') # True!
print(f'NaN is NaN: {nan_value is nan_value}') # True
# NaN的运算特性(吸收律)
print('\nNaN的运算特性:')
print(f'NaN + 100: {nan_value + 100}') # NaN
print(f'NaN * 2: {nan_value * 2}') # NaNNaN的比较特性:
NaN == NaN: False
NaN != NaN: True
NaN is NaN: True
NaN的运算特性:
NaN + 100: nan
NaN * 2: nan
import pandas as pd
import numpy as np
# 创建包含缺失值的示例数据
data = {
'股票代码': ['600519.SH', '000858.SZ', '600036.SH', '601318.SH', '000001.SZ'],
'收盘价': [1850.0, np.nan, 45.2, 52.8, np.nan],
'涨跌幅': [0.05, -0.02, np.nan, -0.01, 0.03],
'成交量': [1200, 3500, np.nan, 5600, 2800]
}
df = pd.DataFrame(data)
print('原始数据:')
print(df)原始数据:
股票代码 收盘价 涨跌幅 成交量
0 600519.SH 1850.0 0.05 1200.0
1 000858.SZ NaN -0.02 3500.0
2 600036.SH 45.2 NaN NaN
3 601318.SH 52.8 -0.01 5600.0
4 000001.SZ NaN 0.03 2800.0
每列缺失值数量:
股票代码 0
收盘价 2
涨跌幅 1
成交量 1
dtype: int64
每列缺失值比例(%):
股票代码 0.0
收盘价 40.0
涨跌幅 20.0
成交量 20.0
dtype: float64
完整行数: 2 / 5
理解哪些变量倾向于一起缺失,有助于诊断缺失原因。
| 场景 | 推荐策略 | 理由 |
|---|---|---|
| 缺失<5%且随机 | dropna() |
信息损失小 |
| 关键指标缺失 | dropna(subset=[...]) |
关键指标不可缺 |
| 缺失>50% | 考虑删除该变量 | 信息太少,插补不可靠 |
删除任何缺失值的行:
股票代码 收盘价 涨跌幅 成交量
0 600519.SH 1850.0 0.05 1200.0
3 601318.SH 52.8 -0.01 5600.0
删除全部为缺失值的行:
股票代码 收盘价 涨跌幅 成交量
0 600519.SH 1850.0 0.05 1200.0
1 000858.SZ NaN -0.02 3500.0
2 600036.SH 45.2 NaN NaN
3 601318.SH 52.8 -0.01 5600.0
4 000001.SZ NaN 0.03 2800.0
在收盘价或涨跌幅有缺失的行被删除:
股票代码 收盘价 涨跌幅 成交量
0 600519.SH 1850.0 0.05 1200.0
3 601318.SH 52.8 -0.01 5600.0
用均值填充(均值=649.33):
股票代码 收盘价 涨跌幅 成交量
0 600519.SH 1850.000000 0.05 1200.0
1 000858.SZ 649.333333 -0.02 3500.0
2 600036.SH 45.200000 NaN NaN
3 601318.SH 52.800000 -0.01 5600.0
4 000001.SZ 649.333333 0.03 2800.0
用中位数填充(中位数=52.80):
股票代码 收盘价 涨跌幅 成交量
0 600519.SH 1850.0 0.05 1200.0
1 000858.SZ 52.8 -0.02 3500.0
2 600036.SH 45.2 NaN NaN
3 601318.SH 52.8 -0.01 5600.0
4 000001.SZ 52.8 0.03 2800.0
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd # 导入Pandas数据分析库
index_bric = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/20220820/xlsx/1560916966116974592.xlsx",sheet_name="Sheet1",header=0,index_col=0) #导入数据
print(index_bric.isnull().any()) #查找每一列是否存在缺失值(用isnull函数)
print(index_bric.isna().any()) #查找每一列是否存在缺失值(用isna函数)
print(index_bric[index_bric.isnull().values==True]) #查找存在缺失值所在的行IBOVESPA指数 False
RTS指数 True
Sensex30指数 True
上证综指 True
dtype: bool
IBOVESPA指数 False
RTS指数 True
Sensex30指数 True
上证综指 True
dtype: bool
IBOVESPA指数 RTS指数 Sensex30指数 上证综指
交易日
2019-06-05 95998.75 1303.35 NaN 2861.4181
2019-06-07 97821.26 1325.95 39615.8984 NaN
2019-06-12 98320.88 NaN 39756.8086 2909.3796
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd # 导入Pandas数据分析库
index_bric = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/20220820/xlsx/1560916966116974592.xlsx",sheet_name="Sheet1",header=0,index_col=0) #导入数据
index_bric_dropna = index_bric.dropna() #删除存在缺失值的行数并创建一个新的数据框
print(index_bric_dropna.isnull().any()) # 输出缺失值检查结果
print(index_bric.shape) #查看原数据框的形状参数
print(index_bric_dropna.shape) #查看新数据框的形状参数IBOVESPA指数 False
RTS指数 False
Sensex30指数 False
上证综指 False
dtype: bool
(20, 4)
(17, 4)
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd # 导入Pandas数据分析库
index_bric = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/20220820/xlsx/1560916966116974592.xlsx",sheet_name="Sheet1",header=0,index_col=0) #导入数据
index_bric_ffill = index_bric.fillna(method="ffill") #向前补齐
print(index_bric_ffill.isnull().any()) # 输出缺失值检查结果
print(index_bric_ffill.loc["2019-06-04":"2019-06-12"]) # 输出2019-06-04IBOVESPA指数 False
RTS指数 False
Sensex30指数 False
上证综指 False
dtype: bool
IBOVESPA指数 RTS指数 Sensex30指数 上证综指
交易日
2019-06-04 97380.28 1307.55 40083.5391 2862.2803
2019-06-05 95998.75 1303.35 40083.5391 2861.4181
2019-06-06 97204.85 1319.85 39529.7188 2827.7978
2019-06-07 97821.26 1325.95 39615.8984 2827.7978
2019-06-10 97466.69 1335.71 39784.5195 2852.1302
2019-06-11 98960.00 1343.33 39950.4609 2925.7162
2019-06-12 98320.88 1343.33 39756.8086 2909.3796
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd # 导入Pandas数据分析库
index_bric = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/20220820/xlsx/1560916966116974592.xlsx",sheet_name="Sheet1",header=0,index_col=0) #导入数据
index_bric_bfill = index_bric.fillna(method="bfill") #向后补齐
print(index_bric_bfill.isnull().any()) # 输出缺失值检查结果
print(index_bric_bfill.loc["2019-06-04":"2019-06-13"]) # 输出2019-06-04IBOVESPA指数 False
RTS指数 False
Sensex30指数 False
上证综指 False
dtype: bool
IBOVESPA指数 RTS指数 Sensex30指数 上证综指
交易日
2019-06-04 97380.28 1307.55 40083.5391 2862.2803
2019-06-05 95998.75 1303.35 39529.7188 2861.4181
2019-06-06 97204.85 1319.85 39529.7188 2827.7978
2019-06-07 97821.26 1325.95 39615.8984 2852.1302
2019-06-10 97466.69 1335.71 39784.5195 2852.1302
2019-06-11 98960.00 1343.33 39950.4609 2925.7162
2019-06-12 98320.88 1346.98 39756.8086 2909.3796
2019-06-13 98773.70 1346.98 39741.3594 2910.7406
import numpy as np
import pandas as pd
# 创建包含缺失值的时间序列数据
dates = pd.date_range('2024-01-01', periods=10)
ts_data = pd.DataFrame({
'日期': dates,
'价格': [10.5, np.nan, np.nan, 11.2, np.nan,
11.8, np.nan, np.nan, 12.5, 12.8]
})
# 线性插值:在两个已知点之间画直线
ts_linear = ts_data.copy()
ts_linear['价格_线性插值'] = ts_linear['价格'].interpolate(method='linear')
print('线性插值:')
print(ts_linear)线性插值:
日期 价格 价格_线性插值
0 2024-01-01 10.5 10.500000
1 2024-01-02 NaN 10.733333
2 2024-01-03 NaN 10.966667
3 2024-01-04 11.2 11.200000
4 2024-01-05 NaN 11.500000
5 2024-01-06 11.8 11.800000
6 2024-01-07 NaN 12.033333
7 2024-01-08 NaN 12.266667
8 2024-01-09 12.5 12.500000
9 2024-01-10 12.8 12.800000
import pandas as pd
import numpy as np
# 模拟招商银行停牌场景
dates = pd.date_range('2024-01-01', periods=10)
suspension_data = pd.DataFrame({
'日期': dates,
'收盘价': [10.5, 10.8, 11.0, np.nan, np.nan,
np.nan, np.nan, np.nan, 11.5, 11.7]
})
suspension_data['是否停牌'] = suspension_data['收盘价'].isna()
print('停牌数据:')
print(suspension_data)停牌数据:
日期 收盘价 是否停牌
0 2024-01-01 10.5 False
1 2024-01-02 10.8 False
2 2024-01-03 11.0 False
3 2024-01-04 NaN True
4 2024-01-05 NaN True
5 2024-01-06 NaN True
6 2024-01-07 NaN True
7 2024-01-08 NaN True
8 2024-01-09 11.5 False
9 2024-01-10 11.7 False
策略1 - 前向填充:
日期 收盘价 是否停牌
0 2024-01-01 10.5 False
1 2024-01-02 10.8 False
2 2024-01-03 11.0 False
3 2024-01-04 11.0 True
4 2024-01-05 11.0 True
5 2024-01-06 11.0 True
6 2024-01-07 11.0 True
7 2024-01-08 11.0 True
8 2024-01-09 11.5 False
9 2024-01-10 11.7 False
策略2 - 删除停牌行:
日期 收盘价 是否停牌
0 2024-01-01 10.5 False
1 2024-01-02 10.8 False
2 2024-01-03 11.0 False
8 2024-01-09 11.5 False
9 2024-01-10 11.7 False
| 策略 | 优点 | 缺点 |
|---|---|---|
| 前向填充 | 简单直观,保持时间连续性 | 低估真实波动率,延迟反映价格跳跃 |
| 删除行 | 避免虚假数据 | 破坏时间连续性,丢失信息 |
金融实践建议:
import numpy as np
import pandas as pd
# 创建测试数据(带噪声的正弦波)
x = np.linspace(0, 10, 20)
y_true = np.sin(x) + np.random.normal(0, 0.1, 20)
# 人为制造缺失值
y_missing = y_true.copy()
y_missing[np.random.choice(20, 5, replace=False)] = np.nan
# 线性插值 vs 样条插值
y_linear = pd.Series(y_missing).interpolate(method='linear')
y_spline = pd.Series(y_missing).interpolate(method='cubic')
# 计算MSE
print(f'线性插值MSE: {np.mean((y_true - y_linear)**2):.4f}')
print(f'样条插值MSE: {np.mean((y_true - y_spline)**2):.4f}')线性插值MSE: 0.0075
样条插值MSE: 0.0056
isna() / isnull() 检测,sum() 统计dropna(how='any') / dropna(subset=[...]) 按需选择fillna(0) / fillna(均值) / fillna(中位数)fillna(method='ffill') 前向 / fillna(method='bfill') 后向interpolate(method='linear') 线性 / 'cubic' 样条[商业大数据分析与应用]